package com.wheel.mybatis.plugins;

import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;

@Intercepts({@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}), @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class})})
public class PropertyNamingStrategyInterceptor implements Interceptor {

    private List<PropertyNamingStrategy> actions;

    public PropertyNamingStrategyInterceptor(String actions) {
        String[] split = actions.split(",");
        this.actions = new ArrayList<>(split.length);
        for (String action : split) {
            this.actions.add(PropertyNamingStrategy.valueOf(action));
        }
    }

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
        List<ResultMap> resultMaps = ms.getResultMaps();
        ResultMap resultMap = resultMaps.get(0);
        Class<?> type = resultMap.getType();
        if (type.isAssignableFrom(Map.class)) {// 先执行得到结果，再对结果进行处理
            @SuppressWarnings("unchecked")
            List<Map<String, Object>> list = (List<Map<String, Object>>) invocation.proceed();
            for (PropertyNamingStrategy action : actions) {
                for (Map<String, Object> object : list) {
                    change(object, action);
                }
            }
            return list;
        } else {
            return invocation.proceed();
        }
    }

    private void change(Map<String, Object> object, PropertyNamingStrategy action) {
        for (String key : object.keySet()) {
            String translate = action.translate(key);
            object.put(translate, object.get(key));
            object.remove(key);
        }
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties arg0) {
    }

    public enum PropertyNamingStrategy {
        ToLowerCase, //
        CamelCase, //
        PascalCase, //
        SnakeCase, //
        KebabCase;

        public String translate(String propertyName) {
            switch (this) {
                case ToLowerCase: {
                    return propertyName.toLowerCase();
                }
                case SnakeCase: {
                    StringBuilder buf = new StringBuilder();
                    for (int i = 0; i < propertyName.length(); ++i) {
                        char ch = propertyName.charAt(i);
                        if (ch >= 'A' && ch <= 'Z') {
                            char ch_ucase = (char) (ch + 32);
                            if (i > 0) {
                                buf.append('_');
                            }
                            buf.append(ch_ucase);
                        } else {
                            buf.append(ch);
                        }
                    }
                    return buf.toString();
                }
                case KebabCase: {
                    StringBuilder buf = new StringBuilder();
                    for (int i = 0; i < propertyName.length(); ++i) {
                        char ch = propertyName.charAt(i);
                        if (ch >= 'A' && ch <= 'Z') {
                            char ch_ucase = (char) (ch + 32);
                            if (i > 0) {
                                buf.append('-');
                            }
                            buf.append(ch_ucase);
                        } else {
                            buf.append(ch);
                        }
                    }
                    return buf.toString();
                }
                case PascalCase: {
                    char ch = propertyName.charAt(0);
                    if (ch >= 'a' && ch <= 'z') {
                        char[] chars = propertyName.toCharArray();
                        chars[0] -= 32;
                        return new String(chars);
                    }

                    return propertyName;
                }
                case CamelCase: {
                    char ch = propertyName.charAt(0);
                    if (ch >= 'A' && ch <= 'Z') {
                        char[] chars = propertyName.toCharArray();
                        chars[0] += 32;
                        return new String(chars);
                    }

                    return propertyName;
                }
                default:
                    return propertyName;
            }
        }
    }
}
